package org.viettan.android.main.managers;

import java.io.IOException;
import java.util.List;

import org.viettan.android.main.R;
import org.viettan.android.main.VietTanApplication;
import org.viettan.android.main.io.EntityRetrievalException;
import org.viettan.android.main.io.Logger;
import org.viettan.android.main.io.NetworkException;
import org.viettan.android.main.io.NetworkUtility;
import org.viettan.android.main.io.UrlUtility;
import org.viettan.android.main.model.RadioEpisode;
import org.viettan.android.main.model.RadioProgram;
import org.viettan.android.main.services.IRadioService;
import org.viettan.android.main.services.RadioProgressListener;
import org.viettan.android.main.services.RadioService;
import org.viettan.android.main.xml.RadioEpisodeXmlParser;
import org.xmlpull.v1.XmlPullParserException;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;

import com.google.android.apps.analytics.GoogleAnalyticsTracker;
import com.google.common.base.Preconditions;
import com.google.inject.Inject;
import com.google.inject.Singleton;

/**
 * Singleton to manage retrieving the radio program and feed from the Viet Tan website feed.
 */
@Singleton
public class RadioManager {

    private final UrlUtility urlUtility;
    private final NetworkUtility networkUtility;
    private final RadioEpisodeXmlParser xmlParser;
    private final Logger logger;
    private final ServiceConnection serviceConnection;
    private final DialogManager dialogManager;
    private IRadioService radioService;
    private RadioEpisode currentEpisodePlaying;
    private RadioProgressListener.Stub listenerToAddOnConnection;

    @Inject
    RadioManager(UrlUtility urlUtility, NetworkUtility networkUtility,
	    RadioEpisodeXmlParser xmlParser, final Logger logger, DialogManager dialogManager) {
	this.urlUtility = urlUtility;
	this.networkUtility = networkUtility;
	this.xmlParser = xmlParser;
	this.logger = logger;
	this.dialogManager = dialogManager;

	serviceConnection = new ServiceConnection() {
	    public void onServiceConnected(ComponentName name, IBinder service) {
		radioService = IRadioService.Stub.asInterface(service);
		logger.info("Connected to RadioService");

		if (listenerToAddOnConnection != null) {
		    RadioManager.this.addRadioProgressListener(listenerToAddOnConnection);
		    listenerToAddOnConnection = null;
		}

		if (currentEpisodePlaying != null) {
		    RadioManager.this.play(currentEpisodePlaying);
		}
	    }

	    public void onServiceDisconnected(ComponentName name) {
		logger.info("Disconnected from RadioService");
		radioService = null;
	    }
	};
    }

    /**
     * Returns the list {@link RadioEpisode}s of the last two weeks bucked into daily
     * {@link RadioProgram}s in reverse-chronological order.
     *
     * @param useCache
     *            retrieve the data from the cache if it is there
     * @return List of {@link RadioEpisode}s recently uploaded
     * @throws EntityRetrievalException
     */
    public List<RadioProgram> getAllPrograms(boolean useCache) throws EntityRetrievalException {
	// download XML feed
	String xml;
	try {
	    xml = networkUtility.httpGet(urlUtility.getRadioFeed(), useCache);

	    // parse the XML for results
	    return xmlParser.parse(xml);
	} catch (NetworkException e) {
	    throw new EntityRetrievalException(e);
	} catch (XmlPullParserException e) {
	    throw new EntityRetrievalException(e);
	} catch (IOException e) {
	    throw new EntityRetrievalException(e);
	}
    }

    /**
     * Starts playing a new {@code RadioEpisode} from the beginning.
     *
     * @param radioEpisode
     */
    public synchronized void play(RadioEpisode radioEpisode) {
	Preconditions.checkArgument(radioEpisode != null, "RadioEpisode cannot be null");

	currentEpisodePlaying = radioEpisode;
	Context appContext = VietTanApplication.getAppContext();

	// start the service first if needed
	if (radioService == null) {
	    Intent startServiceIntent = new Intent(RadioService.class.getName());
	    appContext.startService(startServiceIntent);
	    appContext.bindService(startServiceIntent, serviceConnection, 0);
	    // once the connection is established, play() will be called again
	    return;
	}

	try {
	    radioService.play(currentEpisodePlaying);
	    dialogManager.showLong(R.string.radio_buffering);
	    GoogleAnalyticsTracker.getInstance().trackEvent("Radio" /* Category */,
		    "Play" /* Action */, currentEpisodePlaying.getTitle() /* Label */,
		    currentEpisodePlaying.getId() /* Value */);
	} catch (RemoteException e) {
	    logger.severe("Unable to play: " + e.getMessage());
	}
    }

    /**
     * Resumes playing a radio episode that has been paused.
     */
    public synchronized void resume() {
	if (currentEpisodePlaying == null) {
	    return;
	}
	if (radioService == null) {
	    return;
	}

	try {
	    radioService.resume();
	} catch (RemoteException e) {
	    logger.severe("Unable to resume: " + e.getMessage());
	}
    }

    /**
     * Pauses any currently playing episode (so that we can resume it later at the same position).
     */
    public synchronized void pause() {
	if (radioService == null) {
	    return;
	}

	try {
	    radioService.pause();
	} catch (RemoteException e) {
	    logger.severe("Unable to pause: " + e.getMessage());
	}
    }

    /**
     * Seeks the current playback to a specific position
     *
     * @param milliSeconds
     *            position in ms to seek to
     */
    public void seekTo(int milliSeconds) {
	if (radioService == null) {
	    return;
	}
	if (currentEpisodePlaying == null) {
	    return;
	}

	try {
	    radioService.seekTo(milliSeconds);
	} catch (RemoteException e) {
	    logger.severe("Unable to pause: " + e.getMessage());
	}
    }

    /**
     * Stops any currently playing episode and stops the radio service.
     */
    public synchronized void stop() {
	if (radioService == null) {
	    return;
	}

	try {
	    radioService.stop();
	} catch (RemoteException e) {
	    logger.severe("Unable to stop: " + e.getMessage());
	}

	Context appContext = VietTanApplication.getAppContext();
	appContext.unbindService(serviceConnection);
	Intent stopServiceIntent = new Intent(RadioService.class.getName());
	appContext.stopService(stopServiceIntent);
	radioService = null;
    }

    /**
     * Gets the {@code RadioEpisode} that is currently playing.
     */
    public RadioEpisode getCurrentlyPlaying() {
	return currentEpisodePlaying;
    }

    /**
     * Adds a listener who wants to be notified of radio progress updates.
     *
     * @param listener
     */
    public synchronized void addRadioProgressListener(RadioProgressListener.Stub listener) {
	Preconditions.checkArgument(listener != null, "Listener cannot be null");

	// a bit of HACK: there is a race condition here if multiple folks want to
	// add themselves as listeners, only the last one will prevail.
	if (radioService == null) {
	    listenerToAddOnConnection = listener;
	    return;
	}

	try {
	    radioService.addListener(listener);
	} catch (RemoteException e) {
	    logger.severe("Unable to add listener: " + e.getMessage());
	}
    }

    /**
     * Removes a listener from the progress updates events.
     *
     * @param listener
     */
    public synchronized void removeRadioProgressListener(RadioProgressListener.Stub listener) {
	Preconditions.checkArgument(listener != null, "Listener cannot be null");
	if (radioService == null) {
	    return;
	}

	try {
	    radioService.removeListener(listener);
	} catch (RemoteException e) {
	    logger.severe("Unable to remove listener: " + e.getMessage());
	}
    }
}
